松本行弘的程序世界 13 关于数据的持久化

持久化数据的方法

保存文本

变换成文本的Marshal

将对象按一定的方式变换成文本,就可以保存到文件中去。这样的对象文本化就称为serialize(序列化),或是marshal(封送处理)。

使用Marshal模块

标准Ruby中,嵌入了marshal功能,这就是Marshal模块。
Marshal模块中提供了几乎能将全部Ruby对象变为字节串的方法dump,以及将字节串恢复成原对象(的复制)的load方法。
对象可以简单地保存到文件里。

复制有两种方式

使用Marshal可以完成对象的深复制。
复制对象的时候,通常使用clone方法。这种情况下,只复制直接对象,引用的对象不复制。称为浅复制。
深复制连同引用对象也一起进行递归复制。

仔细看Marshal的格式

Marshal用二进制形式将对象文本化。

屏幕快照 2018-07-23 上午11.07.00
屏幕快照 2018-07-23 上午11.07.00

屏幕快照 2018-07-23 上午11.09.40
屏幕快照 2018-07-23 上午11.09.40

不能保存的3类对象

Marshal在实现上有限制。以下3类对象不能保存:

  1. 定义了特异方法的对象。
  2. 输入、输出或是套接字等不能超越进程保存的对象。
  3. 在扩充库中定义,Ruby不知道保存方法的对象。

但是即使不能封送处理,若不是像输入输出那种从原理上不可能的情况,单纯是不知道封送处理方法的话,重新教一遍也就行了。

制作面向对象数据库

使用Marshal保存对象,使对象具有了持久性。所以,Marshal也可应用于面向对象数据库。
PStore库是Marshal的一个用例。Marshal虽然只是将数据变换成字符串,PStore却利用了这一点,简单地实现了面向对象数据库。
PStore有三个特征:使用Marshal,可以原封不动地保存任意的Ruby对象;具有容易使用的接口;有事务处理(transaction)。
PStore也有缺点,它不适合一下子将数据全部读入内存的大规模数据库。但几百字节的小规模数据库,应该没问题。

试用PStore

打开数据库

开始事务处理

对象的登录和取得

事务处理的终止

简单说明一下事务处理的步骤:

  1. 用flock将数据文件加锁。
  2. 用Marshal从数据文件中读取数据
  3. 执行(事务处理)块
  4. 块的执行成功,Marshal将数据写入数据文件
  5. 块的执行失败,什么也不做。

变换为文本的YAML

Marshal的变换结果是二进制文件,内容不容易看懂。有些场合及时效率低一些,也需要能够以更容易看懂的形式输出。能够满足要求的是YAML。使用文本形式,不依赖平台的体系结构,是一种对人而言易读易编辑的序列化格式。

有以下几个特征:记述简洁;结果容易读懂;使用缩进的层次表现;数据表现是专用的,不必烦恼标签的名称问题。

YMAL可以活用在Ruby on Rails的配置文件等各种各样的领域。YMAL是在Perl中开发的,但正式的支持,Ruby是第一个。

用YAML制作数据库

类似于PStore的东西,YAML::Store,其与PStore的互换性非常高,只要把名字换一换,面向PStore的程序在YAML::Store也能运行。

他俩的区别:

数据格式

很显然,一个YAML,一个Marshal。

数据量

Marshal比YAML紧凑的多,Marshal牺牲了易读性而实现了良好性能。

执行速度

性能优良不光是容量的问题。使用Marshal的PStore比YAML::Store速度高,在这一点上,也是数据量越大,两者的差异就越显著。

对象的保存

对象持久化库Madeleine,利用直接持久化对象的设计模式Object Prevalence。

Madeleine是Object Prevalence在Ruby中的实现,应称为PStore的发展形式。

PStore只是对象单纯由Marshal输出而来,Madeleine则与应用程序相协调,实现了高可靠性和高性能的持久化。

高速的Object Prevalence

所谓Prevalence,是一种实现应用程序持久化和进程间共享数据的设计模式。高性能的秘密在于直接访问内存中的数据。Object Prevalence将处理的数据保存在正在执行的应用程序的内存中,检索等操作不通过SQL而是直接进行,节省了与数据库副武器的通信成本,引用当然就会很高速。

但是,只有是同一进程,才能引用内存中的数据,进程一结束,数据马上消失。从持久化角度有必要解决这一问题。

Object Prevalence用日志记录(journaling)和快照(snapshot)来解决这一问题。Object Prevalence中,数据更新时不是直接更新对象,而是创建称为command的对象,采用的是一种非常间接的方法,在用command更新对象时,内存中的对象更新的同时,所有的更新内容也会写到称为日志(journal log)的外部文件中。

长此下去日志越来越大,所以要将现在数据状态写到称为快照的文件中。有了快照,老日志就不需要了,可在适当的时机删除。

有了最新的快照和最新的日志,可以完全恢复现在对象的状态。程序启动,按三步骤恢复内存的数据。及时有多个进程,只要写入日志的信息是完整的,就可以共享对象的状态。

  1. 如果不存在快照,就初始化应用程序数据。
  2. 如果存在快照,就读入其中最新的一个。
  3. 如果还存在日志,也将其读入,并用其中最新的一个更新应用程序数据。

Object Prevalence的问题点

Object Prevalence通过使用日志记录和快照实现了对象的持久化和进程间共享。Object Prevalence将所有数据都保存到内存中,随着数据量的增大,内存的消耗也在增大。

关系数据库中,不引用的数据放在文件中,必要的内存量就不用那么多了。

Object Prevalence有为了数据更新而具有的特殊结构,更新持久化数据时需要经由command对象。

使用Madeleine

访问时刻信息

让Madeleine更容易使用

Madeleine既保持简洁性,又能让对象持久化,但是最大的缺点是在每次更新应用程序时必须生成command对象。

Madeleine的实用例Instiki

Madeleine没有得到广泛应用,除了知道的人少,还因为数据全保存在内存中,就必须十分留意数据的大小。

Madeleine有一个很大的缺点,就是没有考虑多个进程同时更新数据的情况。

关于XML的考察

XML的祖先是SGML

SGML是将文档电子化的一种格式。由三部分组成:表示数据本身的Instance,表示数据结构的DTD,以及SGML声明。

由于SGML太复杂,处理成本太高,为了表现网页,将SGML特化为HTML,随之诞生的是XML。

XML不像HTML那样是为了特定目的的标记语言,它一开始就是为了通用目的而设计的。为了让XML在没有DTD来定义语法或提供schema信息的情况下,也能够解析,人们对其语法进行了简化。

XML是树结构的数据表现

XML基本上是纯文本,以类似于HTML的标签嵌套方式实现树结构。XML是继承了SGML的通用标记语言,其与SGML最大的区别是其基本语法固定,不依赖于DTD那样的外部信息也能解析。

即使没有标签的概要信息也能解析的语法称为良构的(well-formed),这是XML的一大特征。

优点在于纯文本

最大的优点在于XML基本上是纯文本的,表示结构的信息附加在标签里。

第二个优点是不易发生字符编码的问题。XML规定,在没有明确指定的情况下,字符编码均使用Unicode。

第三个优点是得益于良构的性质,在没有数据结构的情况下也能解析XML数据。这样就可以不考虑目的,而用共同的工具来处理XML数据。

第四个优点在于,XML与其解析工具不依赖于特定的语言,比如Java生成的XML数据在Ruby中的解析也很简单。解析XML的API,像DOM和SAX都超越语言提供了几乎共通的性质,所以不同语言也可以进行同样的操作。

最后一个有点是,人们也很容易理解。

总之,XML作为各种数据交换格式的框架,具有优良的性质。作为格式的格式,也就是元格式,是很优秀的。

缺点在于冗长

最大的缺点是效率低下。XML是以纯文本出现的,标签信息反复出现,显得冗长。与表示相同信息的二进制数据相比,XML数据的容量要大得多,与其他文本表现方式相比(YAML,JSON)也显得冗长。

效率低下不光体现在数据大小上,解析XML的效率也不怎么高。与二进制文件相比,XML文件的解析因为含有大量字符串处理,而容易变得很慢。

作为文本的标记语言而诞生的XML,用其表现有一定结构的数据到底好不好还是个疑问。如果只是用于表现构造数据,比XML更有效率的格式还有很多。而且XML原则上只能表现树结构的数据。

总结,XML作为出于通用目的的数据格式,效率很低,所谓很多优点,如果场合不对,也没多大意义。适才适用,XML也要分情况适当使用。

不适合重视效率的处理

对于重视通信量和速度的情况都不适合,此时应使用专用的协议或是效率更高的格式。

像配置文件那样靠人直接编辑的数据也不推荐XML。配置文件中,需要用到XML的树结构数据地方很少,随着要素数增加,就会很难读,用YMAL和JSON才更合适。

XML适合的场合:

  1. 人一般不直接接触
  2. 复杂性不成问题
  3. 效率不成为问题
  4. 跨平台

适合于信息交换的格式

利用XML的元格式性质,以XML为基础的格式的例子。

  1. RSS。Web网站更新信息。
  2. Atom。RSS的代替。
  3. ebXML。电子商务数据交换。
  4. SVG。向量-图像表示。
  5. SMIL。 多媒体及内容控制。

以上这些都具有XML的性质,可以用XML处理工具简单地解析。制作数据格式时,最麻烦的就是制作处理这种格式的软件。所以,XML与XML处理库的存在是很可贵的。

另外,XML数据库中,问题不在于数据是不是实际以纯文本XML来表现,而在于XML能够表现的树结构能够自由自在地操作。即,不是带标签的纯文本,而是由带属性、带内容的节点所构成的树结构本身才是最重要的。关系数据库的表只能表示间接数据,如果是树结构,可以直接操作直接表现的数据。

XML的解析

XML的解析方法有好几种。

DOM

DOM是文档对象模型的缩写,是对读取了XML数据的树结构进行操作的库。

SAX

Simple API for XML,与将数据全部读入内存的DOM不同,通常,SAX以数据流的形式读入XML,以事件驱动进行处理。SAX中,没必要将数据全部读入,这样往往处理效率更高,所以适合于将XML变换为其他形式的处理,反过来说,不适合于对树结构进行随机访问等用途。

XPath

XPath是用于指定XML树的一部分的书写格式。使用XPath,可以用节点名、属性名或是属性值等来选择特定的节点(群)。

XML处理库REXML

REXML是Ruby标准附属的XML处理库。REXML是具有DOM、SAX、SAX2以及XML Pull Parser等多照片那个功能的库。全部用Ruby实现,所以速度表现不怎么优秀。在特别重视效率的情况下,有必要用libxml等别的XML处理库。

XML的代替

JSON(JavaScript Object Notation)

JSON是把JavaScript的对象记法作为表现格式来使用。

将JSON数据原封不动地作为JavaScript去执行,就可以得到数据表现所对应的对象。但是JSON数据从外部读取的情况较多,实际上作为JavaScript直接执行容易引起安全上的问题,即使效率稍稍低一些,也应当使用解析JSON的库。

Ruby支持JSON。

YAML(YMAL ain’t Markup Language)

YAML是作为XML的对立面而诞生的,具有以下特征。完全放弃标记性记述,专注于数据表现;以缩进为基础表现数据结构;不要标签;可以对应各种语言。文件后缀为.yml

在用作数据表现及配置文件时,具有易读和不易变复杂等优点。实际上,YAML在Ruby on Rails中广泛用于配置文件。

另一方面,YAML到底是数据表现语言,没有相当于schema的东西,不适合于带结构的文本表现及元数据格式。

活用记号和缩进的YMAL比JSON更简洁,正如其名,YMAL不是标记语言,需要使用标记语言时还是XML合适。

1
YAML是JSON的超集,采用空格来作为结构,JSON则是括号,一般YAML解析速度高于JSON,但对于某些东西定义的复杂性高于JSON,速度差别不大时可考虑用JSON。

Binary XML

与通常的XML有等价意义,但效率更高,采用二进制表现的是Binary XML。但现在还没有Binary XML的标准规格。

Protocol Buffer

Protocol Buffer使用一种“数据描述语言”来定义数据结构,然后从这个定义生成一个库,将原始数据变为二进制表现(序列化)。

1
2
持久数据的重要性
如果不是有了像纸和刻了文字的石头等经久不烂而且可以读出的媒介,将来人类文明说不定会遇到失去重要信息的危险。